﻿/* 
 *  Created By: Tom Shirley
 *  Project URL: http://www.codeplex.com/accesschecker
 *  License: GNU GPL v2
 */
using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
using Microsoft.SharePoint.WebControls;
using Microsoft.SharePoint.WebPartPages;
using Microsoft.SharePoint.Utilities;
using System.Globalization;
using System.Reflection;

namespace TomShirley.WebParts.AccessChecker
{

    public enum SPListTypeHasGetUserEffectivePermissionsMethod : int
    {
        False = 0,
        True = 1,
        Unknown = 2
    }

    /// <summary>
    /// Abstract TreeNode which represents a securable object in SharePoint.
    /// </summary>
    #region Abstract SPPermissionsTreeNode class
    abstract class SPPermissionsTreeNode : TreeNode
    {
        protected PermissionType _PermissionType; 

        /// <summary>
        /// Get a value which defines whether this node matches permissions or does not match permissions.
        /// </summary>
        public PermissionType PermissionType
        {
            get { return _PermissionType; }
        }

        /// <summary>
        /// Create a base instance SPPermissionsTreeNode, that describes access to a securable object in SharePoint.
        /// </summary>
        /// <param name="text"></param>
        /// <param name="value"></param>
        /// <param name="imageUrl"></param>
        /// <param name="navigateUrl"></param>
        /// <param name="target"></param>
        public SPPermissionsTreeNode(string text, string value, string imageUrl, string navigateUrl, string target)
            : base(text, value, imageUrl, navigateUrl, target)
        {
        }

        /// <summary>
        /// Determine if this node should be hidden in its TreeView.
        /// </summary>
        /// <param name="secObject"></param>
        /// <param name="filterState"></param>
        /// <returns></returns>
        public abstract bool HideNode(ISecurableObject secObject, TreeNodeFilterState filterState);

        /// <summary>
        /// Set the PermissionType property
        /// </summary>
        /// <param name="securableObject"></param>
        /// <param name="parameters"></param>
        protected abstract void SetPermissionType(ISecurableObject securableObject, params Object[] parameters);
    }
    #endregion

    /// <summary>
    /// TreeNode which provides information about a Users access to a securable object in SharePoint.
    /// </summary>
    #region SPUserAccessTreeNode class
    class SPUserAccessTreeNode : SPPermissionsTreeNode
    {
        internal static SPListTypeHasGetUserEffectivePermissionsMethod _SPListTypeHasGetUserEffectivePermissionsMethod = SPListTypeHasGetUserEffectivePermissionsMethod.Unknown;

        public SPUserAccessTreeNode(string text, string value, string imageUrl, string navigateUrl, string target,
                                    ISecurableObject secObject, SPPrincipalInfo principalInfo, SPBasePermissions basePermissions)
            : base(text, value, imageUrl, navigateUrl, target)
        {
            SetPermissionType(secObject, principalInfo, basePermissions);
        }

        public override bool HideNode(ISecurableObject secObject, TreeNodeFilterState filterState)
        {
            bool hideNode = false;

            // If the user wants to hide any nodes that have matching permissions, than:
            // 1. the ReportObect is set to 'User', which means that the node should
            //    be hidden if:
            //    1.1 The user stored in _PrincipalInfo has permission to the securable object.
            //        This has already been evaluated, and is stored in the nodes Value property.
            //    1.2 The node has no children. The n-ary tree is traversed depth-first, and the logic 
            //        in the calling function ExpandWeb() is such that if a node is to be hidden, it
            //        is not added to the parent node. Therefore if a node has children we must show the
            //        current node.
            if (filterState == TreeNodeFilterState.HideMatchingPermissionNodes)
            {
                if (this.ChildNodes != null && this.ChildNodes.Count == 0 && this.Value.Contains(PermissionType.MatchingPermissions.ToString()))
                {
                    hideNode = true;
                }
            }

            else if (filterState == TreeNodeFilterState.HideDifferentPermissionNodes)
            {
                if (this.ChildNodes != null && this.ChildNodes.Count == 0 && this.Value.Contains(PermissionType.DifferentPermissions.ToString()))
                {
                    hideNode = true;
                }
            }

            return hideNode;
        }

        protected override void SetPermissionType(ISecurableObject securableObject, params Object[] parameters)
        {
            bool doesUserHavePermissions;
            SPPrincipalInfo principalInfo = null;
            SPBasePermissions basePermissions = 0;

            foreach (Object parameter in parameters)
            {
                if (parameter is SPPrincipalInfo)
                {
                    principalInfo = (SPPrincipalInfo)parameter;
                }
                else if (parameter is SPBasePermissions)
                {
                    basePermissions = (SPBasePermissions)parameter;
                }
            }

            if (basePermissions == 0 || principalInfo == null)
                throw new ArgumentException(Properties.Resources.Generic_Error_Message);

            if (securableObject is SPWeb)
            {
                SPWeb web = (SPWeb)securableObject;

                doesUserHavePermissions = web.DoesUserHavePermissions(principalInfo.LoginName, basePermissions);
                this._PermissionType = (doesUserHavePermissions ? PermissionType.MatchingPermissions : PermissionType.DifferentPermissions);
            }
            else if (securableObject is SPList)
            {
                SPList list = (SPList)securableObject;
                SPWeb web = null;

                try
                {
                    web = list.ParentWeb;
                    doesUserHavePermissions = DoesUserHaveListPermissions(principalInfo.LoginName, list, web, basePermissions);
                    this._PermissionType = (doesUserHavePermissions ? PermissionType.MatchingPermissions : PermissionType.DifferentPermissions);
                }
                finally
                {
                    if (web != null)
                        web.Dispose();
                }
            }
            else
                throw new ApplicationException(string.Format("{0} SetPermissionString:\r\n{1}", Properties.Resources.Error_In,
                Properties.Resources.Object_Type_Not_Supported));
        }

        /// <summary>
        /// Determines if the user has the specified permissions on a list.
        /// </summary>
        private static bool DoesUserHaveListPermissions(string login, SPList list, SPWeb web, SPBasePermissions permissionMask)
        {
            return ((GetUserListPermissions(login, list, web) & permissionMask) == permissionMask);
        }

        private static SPListTypeHasGetUserEffectivePermissionsMethod DoesListTypeHaveGetUserEffectivePermissionsMethod()
        {
            try
            {
                MethodInfo GetUserEffectivePermissions =
                    typeof(SPList).GetMethod("GetUserEffectivePermissions", new Type[] { typeof(string), typeof(SPBasePermissions) });

                return SPListTypeHasGetUserEffectivePermissionsMethod.True;
            }
            catch (Exception e)
            {
                
            }
            return SPListTypeHasGetUserEffectivePermissionsMethod.False;
        }

        /// <summary>
        /// Gets a user's permission mask on a list.
        /// </summary>
        /// <param name="login">The login.</param>
        /// <param name="list">The list.</param>
        /// <param name="web">The web.</param>
        /// <returns></returns>
        private static SPBasePermissions GetUserListPermissions(string login, SPList list, SPWeb web)
        {
            SPBasePermissions perms;            

            if (SPUserAccessTreeNode._SPListTypeHasGetUserEffectivePermissionsMethod == SPListTypeHasGetUserEffectivePermissionsMethod.Unknown)            
                SPUserAccessTreeNode._SPListTypeHasGetUserEffectivePermissionsMethod = DoesListTypeHaveGetUserEffectivePermissionsMethod();
            
            if (SPUserAccessTreeNode._SPListTypeHasGetUserEffectivePermissionsMethod == SPListTypeHasGetUserEffectivePermissionsMethod.True)
            {
                return list.GetUserEffectivePermissions(login);
            }
            else
            {
                /// This section was created by Gary Lapointe, please see http://stsadm.blogspot.com/2007/10/enumerate-effective-base-permissions.html
                /// Link retreived on 2008-01-26.        
                PropertyInfo requestProp = web.GetType().GetProperty("Request",
                    BindingFlags.NonPublic |
                    BindingFlags.Instance |
                    BindingFlags.InvokeMethod |
                    BindingFlags.GetProperty);
                object request = requestProp.GetValue(web, null);

                MethodInfo getUserToken =
                    request.GetType().GetMethod("GetUserToken",
                    BindingFlags.NonPublic | BindingFlags.Public |
                    BindingFlags.Instance | BindingFlags.InvokeMethod);

                try
                {
                    SPUserToken token = new SPUserToken((byte[])getUserToken.Invoke(request, new object[] { web.Url, login }));
                    Type[] parameters = new Type[] { typeof(SPUserToken), typeof(ISecurableObject) };
                   
                    MethodInfo getPermissions =
                        typeof(SPUtility).GetMethod("GetPermissions", BindingFlags.NonPublic |
                        BindingFlags.Public |
                        BindingFlags.Instance |
                        BindingFlags.InvokeMethod |
                        BindingFlags.Static, null, parameters, null);

                    perms = (SPBasePermissions)getPermissions.Invoke(null, new object[] { token, list });
                }
                catch (TargetInvocationException ex)
                {
                    throw ex.InnerException;
                }
                return perms;
            }
        }
    }
    #endregion

    /// <summary>
    /// TreeNode that indicates whether the securable SharePoint object that it represents, inherits its permissions or has unique permissions defined. 
    /// </summary>
    #region SPPermissionsInheritanceTreeNode class
    class SPPermissionInheritanceTreeNode : SPPermissionsTreeNode
    {
        public SPPermissionInheritanceTreeNode(string text, string value, string imageUrl, string navigateUrl, string target,
                                    ISecurableObject secObject)
            : base(text, value, imageUrl, navigateUrl, target)
        {
            SetPermissionType(secObject);
        }

        public override bool HideNode(ISecurableObject secureableObject, TreeNodeFilterState filterState)
        {
            bool hideNode = false;

            if (filterState == TreeNodeFilterState.HideMatchingPermissionNodes)
            {
                if (this.ChildNodes != null && this.ChildNodes.Count == 0 && !secureableObject.HasUniqueRoleAssignments)
                    hideNode = true;
            }
            else if (filterState == TreeNodeFilterState.HideDifferentPermissionNodes)
            {
                if (this.ChildNodes != null && this.ChildNodes.Count == 0 && secureableObject.HasUniqueRoleAssignments)
                    hideNode = true;
            }

            return hideNode;
        }

        protected override void SetPermissionType(ISecurableObject securableObject, params Object[] parameters)
        {
            this._PermissionType = (securableObject.HasUniqueRoleAssignments ? PermissionType.DifferentPermissions : PermissionType.MatchingPermissions);
        }
    }
    #endregion
}
